Skip to content

Adds a Data Breach History check, via Have I Been Pwned - #334

Open
Eales wants to merge 5 commits into
lissy93:masterfrom
Eales:feat/breach-history
Open

Adds a Data Breach History check, via Have I Been Pwned#334
Eales wants to merge 5 commits into
lissy93:masterfrom
Eales:feat/breach-history

Conversation

@Eales

@Eales Eales commented Aug 9, 2026

Copy link
Copy Markdown

What this does

Web Check tells you how a site is configured today. It says nothing about whether the service behind it has already lost its users' data β€” which is often the more useful thing to know when you are judging a vendor, or working out whether credential stuffing is a live risk for a login page.

This adds a Data Breach History check against the public Have I Been Pwned breach catalogue: which incidents are on record for the domain, when, how many accounts, and which classes of data were exposed.

No API key. The /api/v3/breaches?domain= endpoint is unauthenticated, so this works on every self-hosted instance out of the box, with nothing to configure.

Privacy β€” deliberately the domain-level catalogue only

HIBP exposes three easily-confused things. This uses only the first:

Used here Why
/breaches?domain= β€” the breach catalogue βœ… Public, key-less, domain only
/breachedaccount/{email} ❌ Needs a paid key, and means sending someone's address to a third party
HIBP "domain search" ❌ Needs proof of domain ownership

Only the registrable domain leaves the server. No email address, account or password is submitted, and the check makes no claim about any individual user's data.

Three things the raw feed does not give you for free

1. Subdomains silently miss. HIBP indexes against the registrable domain, so store.adobe.com returns [] while adobe.com returns the breach. The hostname is folded down with psl, the same way whois.js and subdomains.js already do it.

2. Descriptions contain real markup, including anchors with target="_blank":

...password and a password hint in plain text. The unencrypted hints also
<a href="http://www.troyhunt.com/..." target="_blank" rel="noopener">disclosed
much about the passwords</a> adding further to the risk...

Rendering third-party HTML is the exact flaw this tool reports on other people's sites, so tags are stripped server-side rather than trusted downstream.

3. The catalogue is not all equally credible. Of the 1026 entries currently published, 42 are unverified, 3 fabricated, 16 spam lists and 6 stealer logs. Those are kept β€” hiding them would be its own distortion β€” but flagged, so a fabricated entry is never shown with the weight of a confirmed one.

Exposed data classes are tiered by how damaging they are (credentials and financial records, then data enabling targeted attacks, then plain identifiers), which drives both the ordering and the colour coding.

Example

linkedin.com, through the live endpoint:

domain=linkedin.com  breaches=3  accounts=310,098,844  worst=critical  latest=2023-11-04
  - LinkedIn                             2012-05-05  critical  trust=true
      Passwords[critical] Email addresses[medium]
  - LinkedIn Scraped Data (2021)         2021-04-08  medium    trust=true
      Email addresses[medium] Geographic locations[medium] Names[medium] ...
  - LinkedIn Scraped and Faked Data (2023) 2023-11-04 medium   trust=false
      Email addresses[medium] Geographic locations[medium] Names[medium] ...

The faked 2023 entry sorts last and is badged "unconfirmed" rather than presented as a breach.

Advisory integration

A catalogued breach is history, not a live misconfiguration, so it is deliberately never reported as critical β€” putting a 2012 breach beside "MySQL exposed to the internet" would misrepresent what actually needs attention today. Instead:

  • credentials exposed in a confirmed breach β†’ issue, with advice to force a reset and watch for credential stuffing
  • other confirmed breaches β†’ warning
  • unverified / fabricated / spam-list entries β†’ info
  • nothing on record β†’ pass

Tests

28 unit tests using the built-in Node test runner β€” no new dependencies, no config file. They cover domain folding (including multi-part suffixes like .co.uk), HTML stripping and entity decoding, data-class tiering, sorting, the trust flags, and the malformed-input paths.

yarn test   # node --test
β„Ή tests 28
β„Ή pass 28
β„Ή fail 0

A πŸ§ͺ Unit Tests job is wired into .github/workflows/ci.yml alongside the existing lint and typecheck jobs.

Verification

  • yarn test β€” 28/28
  • yarn lint β€” clean
  • yarn typecheck (astro check) β€” 0 errors, 0 warnings, 0 hints
  • yarn build β€” completes
  • prettier --check on every touched file β€” clean
  • Live end-to-end runs through the real route and middleware: adobe.com, store.adobe.com (proving the subdomain fold), a full https://www.linkedin.com/feed URL, a clean domain, and an IP (correctly skipped)
  • Card and Advisory rule both exercised against live HIBP responses

Notes

  • Most domains return nothing, which is the expected result. The card reports that explicitly rather than hiding, matching how the vulnerabilities card reports a clean host.
  • HIBP publishes under CC BY 4.0, so attribution and the licence link ship with the response rather than being hardcoded in the component.
  • A clean result means only that no breach has been catalogued against the domain β€” not that none occurred. That caveat is stated in the in-app docs and the README.
  • Small overlap with CVE panel 2.0: CISA KEV, EPSS and exposure contextΒ #333: both add the same one-line test script to package.json and the same CI job. Whichever lands first, the other reduces to a trivial conflict. Everything else here is new files or additive registry entries.

Eales added 3 commits August 10, 2026 00:24
Web Check can tell you how a site is configured today, but nothing about
whether the service behind it has already lost its users' data. This adds
a check against the public Have I Been Pwned breach catalogue, which is
free and needs no API key, so it works on every self-hosted instance
without configuration.

Only the registrable domain is ever sent upstream. No email address,
account or password leaves the server, and the check makes no claim about
any individual user β€” that would need HIBP's authenticated account API,
which is deliberately not used here.

Three things the raw feed does not give you for free:

- HIBP indexes breaches against the registrable domain, so a lookup for
  store.adobe.com returns nothing while adobe.com returns the breach. The
  hostname is folded down with psl, as the whois and subdomain checks
  already do.
- Breach descriptions contain real markup, including anchors. Rendering
  third-party HTML is the exact flaw this tool reports on other people's
  sites, so the tags are stripped server-side.
- The catalogue mixes confirmed incidents with unverified, fabricated,
  spam-list and stealer-log entries. Those are kept, but flagged, so a
  fabricated entry is never presented with the weight of a real breach.

Exposed data classes are tiered by how damaging they are, so credentials
and financial records sort above plain identifiers.

Adds 28 unit tests, run with `yarn test` via the built-in Node test
runner, and a CI job to go with them.
Shows each catalogued incident with its date, the number of accounts
affected, and the classes of data exposed, colour-coded by how damaging
each class is. Confirmed incidents are badged as verified; unverified,
fabricated, spam-list and stealer-log entries carry an explicit caveat
saying which, so a reader can weigh them accordingly.

A domain with nothing on record says so rather than hiding the card,
which matches how the vulnerabilities card reports a clean host.

HIBP publishes under CC BY 4.0, so the attribution and licence link
travel with the data rather than being hardcoded in the component.
A catalogued breach is history, not a live misconfiguration, so it is
deliberately never reported as critical: putting a 2012 breach next to
"MySQL exposed to the internet" would misrepresent what needs attention
today.

It is still worth surfacing, because it says credentials for this service
have already circulated. A breach that exposed credentials is reported as
an issue with advice to force a reset and watch for credential stuffing,
other confirmed breaches as a warning, and unconfirmed entries as info.
A domain with nothing on record earns a pass.
@netlify

netlify Bot commented Aug 9, 2026

Copy link
Copy Markdown

βœ… Deploy Preview for web-check ready!

Built without sensitive environment variables

Name Link
πŸ”¨ Latest commit 9c6649c
πŸ” Latest deploy log https://app.netlify.com/projects/web-check/deploys/6a79137d2545650008c063bb
😎 Deploy Preview https://deploy-preview-334--web-check.netlify.app
πŸ“± Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
πŸ€– Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Eales added 2 commits August 10, 2026 01:55
Scanning morele.pl reported it clean, which was wrong: it 301s straight
to morele.net, and the 2018 breach of 2.5 million accounts is catalogued
against morele.net. Looking up only what the user typed produces a false
negative for every site whose primary domain differs from the one it is
branded on.

The destination is now resolved alongside the first lookup, so the extra
work is close to free, and both domains are queried when they differ.
Resolution is best-effort: a site that refuses HEAD, hangs or fails just
falls back to the domain that was typed.

The two domains are reported, never merged into one identity. A parked
domain pointing at a large site must not inherit that site's breaches, so
each entry keeps the domain HIBP recorded it against, the card says which
that was, and a note explains that the redirect is why a second domain
was checked.
DocContent rendered <img src={doc.screenshot}> unconditionally, so the
four checks with no screenshot on file β€” get-ip, vulnerabilities,
subdomains and breach-history β€” showed an Example section containing a
broken image.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant